#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from __future__ import absolute_import

import os
import sys

import json
from celery import shared_task

from django.core.mail import send_mail, send_mass_mail
from django.utils import timezone
from .models import DownloadTaskDetail, DownloadTaskResult
from django_celery_results.models import TaskResult

from website.settings import FILE_CACHE_PATH

# 用户资料
from login.models import UserProfile

# 下载功能模块
from ddm.funcs.download import OdpsTableOperation, DownloadOdpsTable, DownloadOssReport


def extract_odps_table_meta(task_id):
    """
    用于更新用户提交下载任务中数据表的 table 的 Meta 信息.
    """

    # 提取 odps 相关信息: project, table
    task_detail = DownloadTaskDetail.objects.filter(task_id=task_id).values('project', 'table')
    project = (task_detail[0]).get('project')
    table = (task_detail[0]).get('table')

    # 收集用于执行提取 Table Meta 程序信息.
    program_info = {'query_project': project,
                    'query_table': table,
                    'is_table_exist': False,
                    'is_success': False,
                    'response': None,
                    'meta': None}

    if table:
        table_obj = OdpsTableOperation(project=project, table=table)
        if table_obj.is_table_exist():
            program_info['is_table_exist'] = True
            try:
                program_info['meta'] = table_obj.get_table_meta()
            except Exception:
                info = sys.exc_info()
                program_info['response'] = '获取 %s.%s Meta 失败原因: %s, %s' % (project, table, info[0], info[1])
            else:
                program_info['response'] = '成功获取 %s.%s 的Meta信息.' % (project, table)
                program_info['is_success'] = True
            finally:
                pass
        else:
            program_info['response'] = "项目空间 %s 不存在数据表 %s." % (project, table)
    else:
        # 若表名不为 None or ''
        program_info['response'] = "参数 table 为 None 或者 空."

    # [1] 更新任务详情信息
    program_exec_info = json.dumps(program_info, ensure_ascii=True)
    DownloadTaskDetail.objects.filter(task_id=task_id).update(table_meta=program_exec_info)

    # [2]设置自动审批规则[不再发送回执邮件]
    deny_approval_flag = False

    if program_info['is_table_exist'] is False:
        # [2.1]若数据表不存在, 则直接拒绝.
        deny_approval_flag = True
        reason = program_info['response']
    else:
        # 根据 Table Meta 信息, 设置自动审批规则
        if program_info['is_success']:
            table_meta_dict = json.loads(program_info['meta'])
            odps_size = table_meta_dict.get('OdpsSize')
            table_records = table_meta_dict.get('RecordNumber')

            if int(table_records) >= pow(10, 7):
                # 若 下载记录条数 大于 1000W
                deny_approval_flag = True
                reason = '下载条数 %d 大于 1000W, 请邮件沟通.' % table_records
            elif float(odps_size) >= 500.0:
                # 若 Odps 数据表大于 500 M (实际物理空间: 3.0 - 5.0 G)
                deny_approval_flag = True
                reason = '预估下载数据量超过 3 GB, 请邮件沟通.'
            else:
                pass

    if deny_approval_flag:
        DownloadTaskResult.objects.filter(task_id=task_id).update(approver='Admin', approval='Deny',
                                                                  reason=reason, status='Failure',
                                                                  receipt_mail=True)
    return program_exec_info


@shared_task(blind=True)
def download_odps_table(username=None, project='shilupan_strategy', table=None, partition=None,
                        limit=5000, columns=None, is_encrypt=False, encrypt_fields=('mobile', 'idNum'),
                        cache_file=None, delimiter=','):
    """
    异步任务方式: 管理员审批后, 直接发布以异步方式进行下载odps 数据表.
    :param username      : 用户名称.
    :param project       : odps 项目空间, 默认: 'shilupan_strategy'
    :param table         : odps 数据表,   默认: 'dual'
    :param partition     : odps 数据表分区
    :param limit         : 设置下载条数限制, 默认: limit=5000
    :param columns       : 设置下载字段限制, 默认: None 即: 全部) or  tuple
    :param is_encrypt    : 是否对表中命案字段进行加密, 默认: False.
    :param encrypt_fields: 等待加密的敏感字段元组, 默认: ('mobile', 'idNum')
    :param cache_file    : 设置缓存文件绝对路径(若为None, 则自动生成文件名.)
    :param delimiter     : 文本分隔符, 默认:  逗号:"," , 竖线: "|", 反斜线: "/"
     """
    odps_obj = DownloadOdpsTable(username=username, project=project, table=table, partition=partition, limit=limit,
                                 columns=columns, cache_file=cache_file, delimiter=delimiter)
    result = odps_obj.download_odps_table(download_mode='tunnel', compression='infer', is_encrypt=is_encrypt,
                                          encrypt_fields=encrypt_fields)

    return result


@shared_task(blind=True)
def download_oss_report(username=None, project='shilupan_strategy', table=None, partition=None,
                        limit=5000, cache_path=None, is_encrypt=False):
    """
    异步任务方式:  管理员审批后, 直接发布以异步方式进行下载征信报告.
    :param username     : 用户名称。
    :param project      : 项目空间, 默认: 'shilupan_strategy'
    :param table        : 数据表.
    :param cache_path   : 下载文件缓存目录.
    :param limit        : 下载条数限制.
    :param is_encrypt   : 是否加密
    :param partition    : 动态指定下载 panbao 中订单表的分区信息.
    :return:
    """
    oss_report = DownloadOssReport(username=username, project=project, table=table, limit=limit, is_encrypt=is_encrypt,
                                   cache_path=cache_path, partition=partition)
    # 下载征信报告详情信息
    message = oss_report.batch_download()

    return message


@shared_task(blind=True)
def timed_report_download_rate():
    """ 定时更新征信报告的下载进度: 百分比."""

    # 检测 1天之内 所有下载征信报告的任务
    one_day_before = timezone.now() - timezone.timedelta(days=1)
    task_list = DownloadTaskDetail.objects.filter(task_type='report').filter(create_time__gt=one_day_before).values()

    if task_list:
        # 下载征信报告的任务列表
        for task_detail in task_list:
            # 根据 task_id 查看 任务状态
            task_id = task_detail.get('task_id')
            save_path = task_detail.get('save_path')
            table_meta = json.loads(task_detail.get('table_meta'))

            # 查看下载 report 任务的执行状态
            result_list = DownloadTaskResult.objects.filter(task_id=task_id).values()

            if result_list:
                result_status = (result_list[0]).get('status')
            else:
                result_status = 'Failure'

            if result_status == 'Execute':
                # 若下载 report 状态为 Execute
                if table_meta.get('is_success'):
                    # 若提取 Meta 信息成功，则更新下载进度
                    meta_dict = json.loads(table_meta.get('meta'))
                    # 预下载征信报告数目
                    pre_download_cnt = int(meta_dict.get('RecordNumber'))
                    # 重置计数器
                    report_cnt = 0
                    # 预下载征信报告进度
                    report_download_rate = 0.0

                    if os.path.isabs(save_path):
                        # 计算 下载进度
                        for report in os.listdir(save_path):
                            file = os.path.join(save_path, report)
                            if os.path.isfile(file):
                                report_cnt += 1
    
                        # 计算下载效率
                        report_download_rate = round(report_cnt / pre_download_cnt, 4)
    
                        # 更任下载进度
                        success_rate = '%s %%' % str(report_download_rate * 100)
                        DownloadTaskResult.objects.filter(task_id=task_id).update(success_rate=success_rate)


@shared_task(blind=True)
def approval_mail(task_id, approval, reason):
    """
    异步任务邮件: 若提交任务被管理员拒绝, 则以邮件通知申请用户.
    :param task_id  : 提交任务 id .
    :param approval : 审批结果(Approve 或 Deny);
    :param reason   : 审批理由(管理员提交任务)
    """

    now = timezone.now().strftime('%Y-%m-%d %H:%M:%S')

    # Mail subject
    subject = "[%s] 关于管理员拒绝数据下载任务的通知 " % now

    # Mail message
    message = "数据下载任务 - 审批详情 \n 审批时间: %s \n审批结果: %s \n 审批理由: %s." % (now, approval, reason)

    # Mail sender [from_email = DEFAULT_FROM_EMAIL]
    from_email = 'etl-test@slooptech.com'

    # Mail Receipt: 提交任务人的邮箱
    user = DownloadTaskResult.objects.filter(task_id=task_id).get()
    recipient_list = list(UserProfile.objects.filter(username=user.username).values_list('email', flat=True))

    # 发送邮件
    mail_return = send_mail(subject, message, from_email, recipient_list)
    if not mail_return:
        return False, '管理员否决下载任务邮件: 发送失败.'
    else:
        return True, '管理员拒绝下载任务邮件: 发送成功.'


@shared_task
def timed_approval_mail():
    """
    Celery 定时任务: 定时检测用户提交任务状态. 然后以邮件通知管理员进行审批.
    """

    # 任务时间
    now = timezone.now().strftime('%Y-%m-%d %H:%M:%S')
    hour = timezone.now().hour
    night_hour_list = [22, 23, 0, 1, 2, 3, 4, 5, 6, 7, 8]

    # 查找所有处于等待状态(Waiting)的任务
    task_list = list(DownloadTaskResult.objects.filter(approval='Waiting').values())

    if hour not in night_hour_list:
        if task_list:
            # 收集任务信息
            task_info_list = list()

            # [2] 构建邮件内容
            subject = "数据审批系统-审批任务通知"
            from_email = "etl-test@slooptech.com"
            receipt_list = ['etl-test@slooptech.com', 'zhoujin@slooptech.com', 'gelele@slooptech.com']

            for task_no, item in enumerate(task_list, start=1):
                # 根据 task_id 更新 任务状态
                task_id = item.get('task_id')
                task_name = item.get('task_name')
                task_user = item.get('username')

                # 任务信息
                task_info = "[时间: %s: 任务编号: %d] 任务详情: %s \t 名称: %s \t 账户 %s.\n" % (now, task_no, task_id,
                                                                                   task_name, task_user)
                task_info_list.append(task_info)

            # 发送邮件
            message = ''.join(task_info_list)
            mail_receipt_info = send_mail(subject, message, from_email, receipt_list)

            if not mail_receipt_info:
                return False, '审批任务通知: 邮件发送失败.'
            else:
                return True, '审批任务通知: 邮件发送成功.'
    else:
        return False, '晚上22:00 - 次日08:00 不再发送邮件.'


@shared_task(blind=True)
def timed_update_task_status():
    """
    Celery定时任务:  定时检测执行任务状态status， 然后更新到 DownloadTaskResult 中任务状态 status .
    """

    # [1] DownloadTaskResult 的任务状态(status='Execute')的任务列表
    task_list = list(DownloadTaskResult.objects.filter(status='Execute').values())

    # [2] 根据 celery 的异步任务
    if task_list:
        for item in task_list:
            # 任务详情
            task_detail = DownloadTaskDetail.objects.filter(task_id=item.get('task_id'))

            # celery 异步任务的数据存储结果表
            celery_task_id = item.get('celery_task_id')
            celery_result = TaskResult.objects.filter(task_id=celery_task_id)

            if task_detail and celery_result:
                if task_detail[0].task_type == 'table':
                    if celery_result[0].status == 'SUCCESS':
                        DownloadTaskResult.objects.filter(celery_task_id=celery_task_id).update(status='Success',
                                                                                                success_rate='100%')
                    else:
                        DownloadTaskResult.objects.filter(celery_task_id=celery_task_id).update(status='Failure',
                                                                                                success_rate='0.0%')
                else:
                    # 征信报告
                    if celery_result[0].status == 'SUCCESS':
                        if celery_result[0].result:
                            # 将 下载 report 的 result 中信息更新到 审批理由中
                            download_oss_report_info = json.loads(celery_result[0].result)
                            success_rate = download_oss_report_info.get('Rate', '--')
                            DownloadTaskResult.objects.filter(celery_task_id=celery_task_id).update(
                                    status='Success', reason=celery_result[0].result, success_rate=success_rate)
                    else:
                        DownloadTaskResult.objects.filter(celery_task_id=celery_task_id).update(status='Failure',
                                                                                                success_rate='0.0%')


@shared_task(blind=True)
def timed_receipt_mail():
    """
    Celery 定时任务: 若数据下载任务执行成功, 则以定时任务(多人回执邮件)通知申请账户.
    """
 
    # [1] 检测当前时间 8 Hour 之内; 执行过的任务; 未发送过回执邮件;的任务.
    task_time = timezone.now() - timezone.timedelta(hours=8)
    task_list = DownloadTaskResult.objects.filter(receipt_mail=False).filter(approval='Approve').filter(
            update_time__gte=task_time).filter(status__in=('Success', 'Failure')).values()

    # [2] 配置邮件
    mail_send_time = timezone.now().strftime('%Y-%m-%d %H:%M')
    from_email = "etl-test@slooptech.com"

    if task_list:
        # [3]构造邮件
        mail_info_list = list()
        task_id_list = list()

        for item in task_list:
            # [3.1] 任务信息
            task_status = item.get('status')
            task_id = item.get('task_id')
            task_id_list.append(task_id)
            task_detail = DownloadTaskDetail.objects.filter(task_id=task_id).get()

            # 邮件主题
            mail_subject = "[%s] 关于下载任务: %s 结果 %s 的通知" % (mail_send_time, task_detail.task_name, task_status)

            # 邮件内容
            if task_status == 'Success':
                mail_message = "任务ID ：%s\n任务名称: %s\n下载结果: %s\n保存路径: %s" % (task_id, task_detail.task_name,
                                                                           task_status, task_detail.save_path)
            else:
                mail_message = "任务ID ：%s\n任务名称: %s\n下载结果: %s" % (task_id, task_detail.task_name, task_status)

            # 邮箱列表
            username = item.get('username')
            recipient_list = list(UserProfile.objects.filter(username=username).values_list('email', flat=True))
    
            # 构造邮件
            mail_info = (mail_subject, mail_message, from_email, recipient_list)
    
            # 收集邮件
            mail_info_list.append(mail_info)
    
        # 发送多人邮件
        mail_receipt_info = send_mass_mail(tuple(mail_info_list), fail_silently=False)

        if not mail_receipt_info:
            return False, '数据下载任务结果详情邮件通知: 回执邮件发送失败.'
        else:
            # 更新回执邮件信息
            DownloadTaskResult.objects.filter(task_id__in=tuple(task_id_list)).update(receipt_mail=True)
    
            return True, '数据下载任务结果详情邮件通知: 回执邮件发送成功.'


@shared_task(blind=True)
def timed_file_cleaned():
    """
    Celery定时任务: 数据保存时长 retain 、下载结束时间 date_done,  以定时任务清理过期的数据表或征信报告.
    """

    # 由 Celery 执行任务：下载成功的数据表或征信报告.
    task_list = TaskResult.objects.filter(status='SUCCESS').filter(task_name__contains='download').values()

    if task_list:

        # 设定执行信息
        message = "Failed"

        for celery_task in task_list:
            # 提取必要任务信息
            celery_task_id = celery_task.get('task_id')
            date_done = celery_task.get('date_done')

            try:
                # 任务结果
                task_result = DownloadTaskResult.objects.filter(celery_task_id=celery_task_id).get()
                # 任务详情
                task_detail = DownloadTaskDetail.objects.filter(task_id=task_result.task_id).get()
            except DownloadTaskResult.DoesNotExist:
                   message = "无法在 DownloadTaskResult 中查找到 对应的 celery_task_id."
            except DownloadTaskDetail.DoesNotExist:
                   message = "无法在 DownloadTaskDetail 中查找到 当前任务对应 task_id ."
            else:
                # 判断文件保存期限(是否超时)
                if date_done + timezone.timedelta(hours=task_detail.retain) < timezone.now():
                    if task_detail.task_type == 'table':
                        # [1] 删除下载数据表文件
                        os.remove(task_detail.save_path)
                        message = "成功删除过期的数据表文件: %s" % task_detail.save_path
                    elif task_detail.task_type == 'report':
                        # [2] 数据征信报告目录
                        os.removedirs(task_detail.save_path)
                        message = "成功删除过期的征信报告目录: %s" % task_detail.save_path
                    else:
                        message = "success"
            finally:
                return message


@shared_task(blind=True)
def timed_disk_status():
    """
    Celery 定时任务检测 磁盘空间使用状况
    """
    # 信息統計 stat
    st = os.statvfs(FILE_CACHE_PATH)

    # 剩余量
    free = round((st.f_bavail * st.f_frsize)/(1024 ** 3), 3)
    # 磁盘空间
    total = round((st.f_blocks * st.f_frsize)/(1024 ** 3), 3)
    # 磁盘使用量
    used = round(((st.f_blocks - st.f_bfree) * st.f_frsize)/(1024 ** 3), 3)
    # 磁盘使用率
    percent = round((float(used) / total), 3)

    # 输入判定规则: 若磁盘使用率 大于 90%
    if (percent >= 0.90) or free < 50.0:

        # 磁盘空间预警邮件
        subject = "服务器磁盘空间存储预警(审批任务平台)"
        message = '磁盘详情: \n 挂载磁盘: %s G\n 磁盘空间: %s G\n 磁盘使用: %s G\n 磁盘剩余: %s G\n 磁盘使用率: %s' % \
                  (FILE_CACHE_PATH, str(total), str(used), str(free), str(percent))
        from_email = "etl-test@slooptech.com"

        # 抄送邮箱
        receipt_list = ['lichenguang@slooptech.com']

        # 发送邮件
        mail_receipt_info = send_mail(subject, message, from_email, receipt_list)
    
        if not mail_receipt_info:
            # 邮件发送失败
            return False, '磁盘空间不足预警邮件: 发送失败.'
        else:
            # 邮件发送成功
            return True, '磁盘空间不足预警邮件: 发送成功.'
